package br.com.felix.easyfileexplorer.ui;

import java.awt.CardLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.ClipboardOwner;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.swing.CellEditor;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTable;
import javax.swing.JTree;
import javax.swing.event.CellEditorListener;
import javax.swing.event.ChangeEvent;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import br.com.felix.commandline.ConsoleLog;
import br.com.felix.easyclearcase.tasks.InterfaceTask;
import br.com.felix.easyclearcase.tasks.SystemCommandTask;
import br.com.felix.easyclearcase.tasks.Task;
import br.com.felix.easyclearcase.tasks.TaskGroup;
import br.com.felix.easyfileexplorer.EasyFile;
import br.com.felix.easyfileexplorer.EasyIcon;
import br.com.felix.util.ILogDevice;
import br.com.felix.util.io.DirectoryFileFilter;
import br.com.felix.util.io.JavaFileListTransferable;

/**
 * A swing component to navigate the filesystem and to some file operations.
 * */
public class EasyFileExplorer extends JPanel{

	private static final long serialVersionUID = 3794675450846015885L;
	
	/**
	 * Root node "My Computer"
	 * */
	private DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode(new File("My Computer"));
	
	/**
	 * Manages thread execution.
	 * */
	private ExecutorService taskExecutor = Executors.newFixedThreadPool(5);
	
	/**
	 * Displays the directory tree structure.
	 * */
	private JTree tree;
	
	/**
	 * Displays files and subfolders of the selected folder on the tree.
	 * */
	private JTable tableFiles;
	
	private JPanel panelFiles;
	
	private JPanel panelFolders;
	
	/**
	 * Customized cellRenderer to render files.
	 * */
	private TableCellRenderer cellRenderer;
	
	/**
	 * PopUp menu that shows when user clicks on a file.
	 * */
	private JPopupMenu popUpFiles;
	
	/**
	 * When multiple requests to display files are fired, only the last one needs to be processed.
	 * */
	private UpdateStatusAfterOperationsTaskGroup lastUpdateStatusTask = null;
	
	/**
	 * Logs what is happening.
	 * */
	private ILogDevice logDevice = new ConsoleLog();
	
	/**
	 * Order files so that directories appear first, files later. Alphabetical order that ignores case.
	 * */
	private Comparator<File> fileComparator = new Comparator<File>(){
		public int compare(File f1, File f2) {
			if(f1.isDirectory()){
				if(f2.isDirectory()){
					return f1.getName().toLowerCase().compareTo(f2.getName().toLowerCase());
				}
				return -1;
			}
			if(f2.isDirectory())return +1;
			return f1.getName().toLowerCase().compareTo(f2.getName().toLowerCase());
		}
	};
	
	
	/**
	 * Constructor. Initializes the component.
	 * */
	public EasyFileExplorer(){
		setLayout(new CardLayout());
		JScrollPane jspFolder = initializeFolderPanel();
		jspFolder.setPreferredSize(new Dimension(400,200));
		panelFiles = new JPanel();
		panelFiles.setLayout(new CardLayout());
		panelFolders = new JPanel();
		panelFolders.setLayout(new CardLayout());
		JSplitPane jspFolderAndFiles = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,panelFolders, panelFiles);
		panelFolders.add(jspFolder,"FOLDERS");
		jspFolderAndFiles.setBackground(new Color(255,0,0));
		jspFolderAndFiles.setDividerLocation(300);
		add(jspFolderAndFiles, "MAIN");
		
		configurePopUpFiles();
	}



	/**
	 * Initializes the popUpMenu for files with file commands.
	 * */
	private void configurePopUpFiles() {
		popUpFiles = new JPopupMenu();
		JMenuItem mniCopy = new JMenuItem("Copy", EasyIcon.COPY.icon());
		JMenuItem mniMove = new JMenuItem("Move to", EasyIcon.CUT.icon());
		JMenuItem mniPaste = new JMenuItem("Paste", EasyIcon.PASTE.icon());
		JMenuItem mniDelete = new JMenuItem("Delete", EasyIcon.DELETE.icon());
		JMenuItem mniRename = new JMenuItem("Rename", EasyIcon.RENAME.icon());
		
		mniCopy.addMouseListener(new MouseAdapter(){
			@Override
			public void mouseReleased(MouseEvent e) {
				copy();
			}
		});
		
		mniPaste.addMouseListener(new MouseAdapter(){
			@Override
			public void mouseReleased(MouseEvent e) {
				DefaultMutableTreeNode node = (DefaultMutableTreeNode)tree.getLastSelectedPathComponent();
				if(node!=null){
					paste(node);
				}
			}
		});
		
		
		mniDelete.addMouseListener(new MouseAdapter(){
			@Override
			public void mouseReleased(MouseEvent e) {
				delete();
			}
		});
		
		mniRename.addMouseListener(new MouseAdapter(){
			@Override
			public void mouseReleased(MouseEvent e) {
				rename();
			}
		});
		
		
		mniMove.addMouseListener(new MouseAdapter(){
			@Override
			public void mouseReleased(MouseEvent e) {
				move();
			}
		});
		
		
		popUpFiles.add(mniCopy);
		popUpFiles.add(mniMove);
		popUpFiles.add(mniPaste);
		popUpFiles.add(mniDelete);
		popUpFiles.add(mniRename);
	}
	
	
	/**
	 * Start the cell editing on the table. When it is finished, renames the selected file.
	 * */
	private void rename() {
		final int row = tableFiles.getSelectedRow();
		tableFiles.editCellAt(row, 0);
		final Object value = tableFiles.getValueAt(row, 0);
		if(value instanceof File){
			final File file = (File)value;
			final CellEditor ce = tableFiles.getCellEditor();
			ce.addCellEditorListener(new CellEditorListener(){
				public void editingCanceled(ChangeEvent e) {
					tableFiles.setValueAt(value, row, 0);
					tableFiles.clearSelection();
					ce.removeCellEditorListener(this);
				}
				public void editingStopped(ChangeEvent e) {
					String name = (String) ce.getCellEditorValue();
					File parent = file.getParentFile();
					File newFile = new File(parent,name);
					if(newFile.exists()){
						ce.cancelCellEditing();
						JOptionPane.showMessageDialog(getParent(),"Could not rename file: another file with the same name already exists.", "Operation failed",JOptionPane.WARNING_MESSAGE);
					}
					else{
						file.renameTo(newFile);
						ce.removeCellEditorListener(this);
					}
					tableFiles.setValueAt(file, row, 0);
				}
			});
		}
		else{
			System.out.println("Value is not file!!!");
		}
			
	}



	/**
	 * Starts a task to delete selected files.
	 * Folders must be empty to be deleted.
	 * */
	protected void delete() {
		UpdateStatusAfterOperationsTaskGroup tgroup = new UpdateStatusAfterOperationsTaskGroup((DefaultMutableTreeNode) tree.getLastSelectedPathComponent());
		Task deleteTask = new Task() {
			@Override
			public void cancel() {
			}
			@Override
			public TaskPriority getTaskPriority() {
				return TaskPriority.HIGH;
			}
			@Override
			public void run() {
				changeStatus(TaskStatus.IN_PROGRESS);
				int [] rows = tableFiles.getSelectedRows();
		        for(int row: rows){
		        	File file = (File) tableFiles.getValueAt(row, 0);
		        	boolean fileDeleted = file.delete();
					if(!fileDeleted){
		        		logDevice.log("File '"+file.getName()+"' was not deleted.");
		        	};
		        }
		        changeStatus(TaskStatus.COMPLETE_SUCCESS);
			}
		};
        
        tgroup.setName("Delete files task group");
        tgroup.add(deleteTask);
        tgroup.startTasks();
	}



	/**
	 * Copies files on the clipboard to the folder at the specified node.
	 * @param node is the node on the tree component that represents the target folder for the files on the clipboard.
	 * */
	protected void paste(DefaultMutableTreeNode node) {
		Clipboard clipboard =  Toolkit.getDefaultToolkit().getSystemClipboard();
		Transferable contents = clipboard.getContents(null);
	    boolean hasTransferableText = (contents != null) && contents.isDataFlavorSupported(DataFlavor.javaFileListFlavor);
	    if (hasTransferableText) {
	      try {
	        List<File> result = (List<File>)contents.getTransferData(DataFlavor.javaFileListFlavor);
	        UpdateStatusAfterOperationsTaskGroup tgroup = new UpdateStatusAfterOperationsTaskGroup(node);
	        File easyFolder = (File)node.getUserObject();
	        tgroup.setName("Paste files task group");
	        String command;
	        SystemCommandTask systemCommandTask;
	        for(File f:result){
	        	if(f.isDirectory()){
	        		command = "cmd.exe /c md \""+ easyFolder.getAbsolutePath()+"\\"+f.getName()+"\" & xcopy \""+f.getAbsolutePath()+"\" \""+easyFolder.getAbsolutePath()+"\\"+f.getName()+"\" /E";
	        	}
	        	else{
	        		command = "cmd.exe /c copy \""+f.getAbsolutePath()+"\" \""+easyFolder.getAbsolutePath()+"\"";	
	        	}
	        	systemCommandTask = new SystemCommandTask(command);
	        	systemCommandTask.setName("Copy file "+f.getName()+" to "+easyFolder.getName());
	        	systemCommandTask.setOutputLog(logDevice);
	        	systemCommandTask.setErrorLogger(logDevice);
				tgroup.add(systemCommandTask);
	        }
	        tgroup.startTasks();
	      }
	      catch (UnsupportedFlavorException ex){
	        //highly unlikely since we are using a standard DataFlavor
	        System.out.println(ex);
	        ex.printStackTrace();
	      }
	      catch (IOException ex) {
	        System.out.println(ex);
	        ex.printStackTrace();
	      }
	    }
		
	}



	/**
	 * Puts the selected files on the system clipboard.
	 * */
	protected void copy() {
		Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
		int[]rows = tableFiles.getSelectedRows();
		List<File>lstFiles = new ArrayList<File>();
		for(int row:rows){
			lstFiles.add((File)tableFiles.getValueAt(row, 0));
		}
		JavaFileListTransferable contents = new JavaFileListTransferable(lstFiles);
		ClipboardOwner owner = new ClipboardOwner() {
			public void lostOwnership(Clipboard clipboard, Transferable contents) {
			}
		};
		clipboard.setContents(contents, owner);
	}



	/**
	 * Initializes the directory structure components.
	 * */
	private JScrollPane initializeFolderPanel() {
		File[] drives = File.listRoots();
		for(File drive: drives){
			rootNode.add(new DefaultMutableTreeNode(new EasyFile(drive)));
		}
		tree = new JTree(rootNode);
		initializeTree();

		JScrollPane scrollPane = new JScrollPane(tree);
		return scrollPane;
	}

	

	/**
	 * Adds the files to the specified node and expand this node.
	 * */
	private void createNodes(DefaultMutableTreeNode node, File[] files) {
		for(File file: files){
			node.add(new DefaultMutableTreeNode(new EasyFile(file)));
		}
		tree.repaint();
		tree.validate();
	}


	
	/**
	 * Initializes the tree component. Initializes the customized cellRenderer.
	 * */
	private void initializeTree() {
		tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
		tree.setEditable(false);
		
		DefaultTreeCellRenderer dtcr = new DefaultTreeCellRenderer(){
			@Override
			public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) {
				super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
				synchronized(this){
					DefaultMutableTreeNode node = (DefaultMutableTreeNode)value;
					File efile = (File)node.getUserObject();
					if(efile.getName().length()==0){
					setIcon(EasyIcon.DRIVE.icon());
				}
				}
				return this;
			}
		};
		final Color selectedColor = dtcr.getBackgroundSelectionColor();
		final Color notSelectedColor = dtcr.getBackgroundNonSelectionColor();
		dtcr.setLeafIcon(EasyIcon.FOLDER.icon());
		dtcr.setClosedIcon(EasyIcon.FOLDER.icon());
		dtcr.setOpenIcon(EasyIcon.FOLDER.icon());
		
		cellRenderer = new DefaultTableCellRenderer(){
			{
				setFont(new Font("Arial",Font.PLAIN,12));	
			}

			public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
				setBackground(isSelected?selectedColor:notSelectedColor);
				if(value instanceof EasyFile){
					EasyFile file = (EasyFile)value;
					setText(file.getName());
					setIcon(file.getIcon());
					return this;
				}
				return super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
			}
		};
		
		
		tree.setCellRenderer(dtcr);
		
		tree.addTreeSelectionListener(new TreeSelectionListener() {
			public void valueChanged(TreeSelectionEvent e) {
				final DefaultMutableTreeNode node = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent();
				if (node == null || node == rootNode) return;
				InterfaceTask itask = new InterfaceTask(){
					public void changeInterface(){
						if(cancelled){
							changeStatus(TaskStatus.CANCELLED);
							return;
						}
						displayFiles(node);
						TreePath tpath = new TreePath(node.getPath());
						tree.expandPath(tpath);
					}
				};
				itask.setName("Display files");
				executeTask(itask);
			}
		});
		tree.addMouseListener(new MouseAdapter() {
			@Override
			public void mousePressed(MouseEvent e) {
				if (e.getButton() == MouseEvent.BUTTON3 && tree.getLastSelectedPathComponent() != null) {
					showPopUpDirectories(e);
				}
			}
		});
		
	}
	

	/**
	 * Does nothing.
	 * */
	private void showPopUpDirectories(MouseEvent e) {
		// TODO Auto-generated method stub
	}

	
	
	/**
	 * Displays the files and subfolders of the selected folder on the tree component.
	 * */
	private void displayFiles(DefaultMutableTreeNode node) {
		File file = (File)node.getUserObject();
		
		File [] subs = file.listFiles(new DirectoryFileFilter());
		if(subs!=null && subs.length>0){
			createNodes(node, subs);
		}
		else{
			TreePath tpath = new TreePath(node.getPath());
			tree.setSelectionPath(tpath);
		}
		
		
		File[] files = file.listFiles();
		final Object[][] rowdata = new Object[files.length][4];
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
		Date lastModified;
		Arrays.sort(files, fileComparator);
		
		for (int i = 0; i < files.length; i++) {
			String name = files[i].getName();
			rowdata[i][0] = new EasyFile(files[i]);
			rowdata[i][1] = files[i].isDirectory()?"directory":name.substring(name.lastIndexOf('.') + 1).toUpperCase();
			lastModified = new Date(files[i].lastModified());
			rowdata[i][2] = sdf.format(lastModified);
			if(files[i].isDirectory()){
				rowdata[i][3] = "";
			}
			else{
				long length = files[i].length();
				long kilobytes = length!=0?length/1024:0;
				if(kilobytes!=0){
					rowdata[i][3] = String.valueOf(kilobytes)+" KB";
				}
				else{
					rowdata[i][3] = String.valueOf(length)+" bytes";
				}
			}
		}
		String columnNames[] = {"File", "Type", "Date", "Size" };
		tableFiles = new JTable(rowdata, columnNames);
		tableFiles.setFillsViewportHeight(true);
		tableFiles.addMouseListener(new MouseAdapter(){
			@Override
			public void mousePressed(MouseEvent e) {
				if(e.getClickCount()>=2){
					tableFiles.editingCanceled(new ChangeEvent(e));
					int selRow = tableFiles.getSelectedRow();
					if (selRow <= 0)selRow = tableFiles.rowAtPoint(e.getPoint());
					if(selRow >= 0){
						Object value = tableFiles.getValueAt(selRow, 0);
						if(value instanceof File){
							File selectedFile = (File)value;
							DefaultMutableTreeNode node = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent();
							DefaultMutableTreeNode childNode;
							File childFile;
							for(int i=0; i<node.getChildCount();i++){
								childNode = (DefaultMutableTreeNode) node.getChildAt(i);
								childFile = (File)childNode.getUserObject();
								if(childFile.equals(selectedFile)){
									TreePath tpath = new TreePath(childNode.getPath());
									tree.setSelectionPath(tpath);
								}
							}
						}
						else{
							System.out.println("Dblclk is not a file");
						}
					}
					else{
						JOptionPane.showMessageDialog(getParent(),"Please select a file.","Operation failed", JOptionPane.WARNING_MESSAGE);
					}
				}
			}
			
		});
		
		
		TableColumn column = tableFiles.getColumn("File");
		column.setCellRenderer(cellRenderer);
		
		tableFiles.setGridColor(new Color(255,255,255));
		tableFiles.addMouseListener(new MouseAdapter(){
			@Override
			public void mousePressed(MouseEvent e) {
				if (e.getButton() == MouseEvent.BUTTON3 && tree.getLastSelectedPathComponent() != null) {
					showPopUpFiles(e);
				}
			}
		});
		tableFiles.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
		tableFiles.setFillsViewportHeight(true);
		tableFiles.setAutoCreateRowSorter(true);
		JScrollPane jspTable = new JScrollPane(tableFiles);
		panelFiles.removeAll();
		panelFiles.add(jspTable, "MAIN");
		panelFiles.validate();
	}




	/**
	 * Displays the popUpMenu at the mouse position.
	 * */
	protected void showPopUpFiles(MouseEvent e) {
		popUpFiles.show(tableFiles, e.getX(), e.getY());
	}


	/**
	 * Schedules a task to be executed.
	 * */
	private void executeTask(Task task){
		taskExecutor.execute(task);
	}


	
	/**
	 * Opens a dialog to select the target directory to move files to.
	 * When the dialog is closed, files and directories are moved.
	 * */
	private void move() {
		JFileChooser jfc = new JFileChooser();
		jfc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
		jfc.showSaveDialog(getParent());
		File targetDirectory = jfc.getSelectedFile();
		
		UpdateStatusAfterOperationsTaskGroup tgroup = new UpdateStatusAfterOperationsTaskGroup((DefaultMutableTreeNode) tree.getLastSelectedPathComponent());
		int rows [] = tableFiles.getSelectedRows();
		tgroup.setName("Move files task group");
		String command;
		SystemCommandTask systemCommandTask;
		File f;
		for(int row:rows){
			f = (File) tableFiles.getValueAt(row,0);
			command = "cmd.exe /c move \""+f.getAbsolutePath()+"\" \""+targetDirectory.getAbsolutePath()+"\"";	
			systemCommandTask = new SystemCommandTask(command);
			systemCommandTask.setName("Move file "+f.getName()+" to "+targetDirectory.getName());
			systemCommandTask.setOutputLog(logDevice);
			systemCommandTask.setErrorLogger(logDevice);
			tgroup.add(systemCommandTask);
		}
		tgroup.startTasks();
	}



	public static void main(String args[]){
		JFrame jfr = new JFrame();
		EasyFileExplorer efe = new EasyFileExplorer();
		Container contentPane = jfr.getContentPane();
		contentPane.setLayout(new CardLayout());
		contentPane.add(efe,"PRINCIPAL");
		jfr.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		jfr.setSize(new Dimension(1024,768));
		jfr.setVisible(true);
	}
	
	
	/**
	 * Updates the file's display after all operations are completed.
	 * */
	class UpdateStatusAfterOperationsTaskGroup extends TaskGroup{
		private DefaultMutableTreeNode file;
		public UpdateStatusAfterOperationsTaskGroup (DefaultMutableTreeNode file){
			this.file = file;
			lastUpdateStatusTask = this;
		}
		
		public void run(){
			if(lastUpdateStatusTask == this){
				changeStatus(TaskStatus.IN_PROGRESS);
				displayFiles(file);
				changeStatus(TaskStatus.COMPLETE_SUCCESS);
			}
			else{
				changeStatus(TaskStatus.CANCELLED);
			}
		}

		@Override
		public void startTasks() {
			for(Task task:tasks){
				executeTask(task);
			}
		}

		@Override
		protected void groupReadyToStart() {
			executeTask(this);
		}
	}


	
}
